/*
* Copyright (c) 2007, Nubo Technologies
* 
* All rights reserved.
* 
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
*    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
*    * Neither the name of the Nubo Technologies nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.nubotech.gwt.oss.client.s3;

import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.Command;
import com.nubotech.gwt.oss.client.AccessControlPolicy;
import com.nubotech.gwt.oss.client.AccessControlPolicyCallback;
import com.nubotech.gwt.oss.client.BucketResultCallback;
import com.nubotech.gwt.oss.client.ObjectHolderResultCallback;
import com.nubotech.gwt.oss.client.ObjectHolder;
import com.nubotech.gwt.oss.client.OnlineStorageService;
import com.nubotech.gwt.oss.client.RequestHeader;
import com.nubotech.gwt.oss.client.auth.Credential;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 *
 * @author jonnakkerud
 */
public class S3OnlineStorageService extends OnlineStorageService {
    private List commands;
    private String host;
    
    public S3OnlineStorageService(Credential c) {
        super(c);
    }

    public void listAllBuckets(BucketResultCallback cb) {   
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setCallbackHandler(new BucketHandler(cb));
        service.doGet("/");
    }

    public void createBucket(final String name) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(name);
        executeCommand(new AwsS3Command(service) {
            public void execute() {
                service.doPut("/");
            }
        });       
    }

    public void createBucket(String name, int accessPolicy) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(name);
        ObjectHolder content = ObjectHolder.EMPTY;
        content.setAccessPolicy(accessPolicy);
        service.doPut("/", content);
    }
    
    public void deleteBucket(String name) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(name);
        service.doDelete("/");
   }


    public void listBucket(String bucketName, Map params, ObjectHolderResultCallback cb) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(bucketName);
        service.setCallbackHandler(new ObjectHolderListHandler(cb));
        service.doGet("/", params);       
    }

    public void putObject(ObjectHolder content, RequestHeader[] headers) {
        // resolve content type
        if (content.getDataAsString() != null) {
            if (RequestHeader.getRequestHeaderValue(RequestHeader.CONTENT_TYPE, headers) == null) {
                // add content-type to request headers
                RequestHeader cth = RequestHeader.CONTENT_TYPE;
                cth.setHeaderValue(content.getContentType());
                if (headers == null) {
                    headers = new RequestHeader[] {cth};
                }
                else {
                    RequestHeader[] h = new RequestHeader[headers.length+1];
                    int i = 0;
                    for (; i < headers.length; i++) {
                        h[i] = headers[i];
                    }
                    h[i++] = cth;
                }
            }
        }
        putObjectImpl(content, headers);
    }
    
    private void putObjectImpl(final ObjectHolder content, final RequestHeader[] headers) {
        // TODO validate ObjectHolder        
        AwsS3 service = new AwsS3(getHost(), getCredential());
        executeCommand(new AwsS3Command(service) {
            public void execute() {
                service.setBucket(content.getBucket().getName());
                service.doPut("/"+content.getKey(), content, headers);               
            }
        });               
    }
    
    public void getObject(String bucketName, String key, RequestHeader[] headers, ObjectHolderResultCallback cb) {
        AwsS3 service = new AwsS3(getHost(), getCredential());        
        service.setBucket(bucketName);
        service.setCallbackHandler(new ObjectHolderHandler(bucketName, key, cb, true));
        service.doGet("/"+key, headers);        
    }
    
    public void getObjectInfo(String bucketName, String key, ObjectHolderResultCallback cb) {
        AwsS3 service = new AwsS3(getHost(), getCredential());        
        service.setBucket(bucketName);
        service.setCallbackHandler(new ObjectHolderHandler(bucketName, key, cb, false));
        service.doHead("/"+key);
    }

    public void deleteObject(String bucketName, String key) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(bucketName);
        service.doDelete("/"+key);
    }

    public String createGetLink(String bucketName, String key, Date expiresDate) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(bucketName);

        StringBuffer sb = new StringBuffer();
        sb.append(service.createUrl(bucketName, "/"+key, null));
        
        // append the params
        sb.append("?AWSAccessKeyId=").append(getCredential().getAccount());
        
        if (expiresDate == null) {
            // expire 5 years from now
            expiresDate = new Date();
            expiresDate.setYear(expiresDate.getYear()+1);
        }
        
        // make date UTC
        expiresDate.setTime(expiresDate.getTime() + (expiresDate.getTimezoneOffset() * 60000)); 
        String expires = String.valueOf(expiresDate.getTime()/1000);        
        
        // Signature
        String signature = service.createSignature(new String[] {"GET", "", "", expires}, "", service.createCanonicalizedResource(bucketName, "/"+key, true));
        sb.append("&").append("Signature=").append(URL.encodeComponent(signature));
        
        // Expires
        sb.append("&").append("Expires=").append(expires);
        
        return sb.toString();
    }

    // TODO test
    public void getAccessControlPolicy(String bucketName, AccessControlPolicyCallback cb) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(bucketName);
        service.setCallbackHandler(new AccessControlPolicyHandler(cb));
        service.doGet("/?acl");
    }

    public void getAccessControlPolicy(String bucketName, String key, AccessControlPolicyCallback cb) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(bucketName);
        service.setCallbackHandler(new AccessControlPolicyHandler(cb));
        service.doGet(key+"/?acl");        
    }

    // TODO test
    public void updateAccessControlPolicy(String bucketName, AccessControlPolicy policy) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(bucketName);
        ObjectHolder content = new ObjectHolder();
        AccessControlPolicySerializer policySerializer = new AccessControlPolicySerializer(policy);
        content.setData(policySerializer.toXml());        
        service.doPut("/?acl", content); 
    }

    public void updateAccessControlPolicy(String bucketName, String key, AccessControlPolicy policy) {
        AwsS3 service = new AwsS3(getHost(), getCredential());
        service.setBucket(bucketName);
        ObjectHolder content = new ObjectHolder();
        AccessControlPolicySerializer policySerializer = new AccessControlPolicySerializer(policy);
        content.setData(policySerializer.toXml());        
        service.doPut(key+"/?acl", content);         
    }
    
    private void executeCommand(AwsS3Command command) {
        // if in batch mode, add to the execution chain
        if (isBatch()) {
            AwsS3CallbackHandler handler = command.service.getCallbackHandler();
            command.service.setCallbackHandler(new ChainedCallbackHandler(handler));
            commands.add(command);
        }
        else {
            command.execute();
        }
    }
    
    public boolean isBatch() {
        return (commands != null);
    }
    
    public void beginBatch() { 
        commands = new ArrayList();
    }
    
    public void commitBatch() {
        if (commands != null && commands.size() > 0) {
            // add the next command to form a chain of commands 
            for (int i = 0; i < commands.size(); i++) {
                AwsS3Command command = (AwsS3Command)commands.get(i);
                int nextCommandIdx = i+1;
                if (nextCommandIdx < commands.size()) {
                    ChainedCallbackHandler handler = (ChainedCallbackHandler) command.service.getCallbackHandler();
                    handler.nextCommand = (Command)commands.get(nextCommandIdx);
                }
            }
            
            // now run the first command in the chain
            ((Command)commands.get(0)).execute();
        }
        commands = null;
    }

    abstract class AwsS3Command implements Command {
        protected AwsS3 service;
        public AwsS3Command(AwsS3 service) {
            this.service = service;            
        }
    }

    @Override
    public void setHost(String host) {
        this.host = host;
    }

    @Override
    public String getHost() {
        if (host == null) {
            host = AwsS3.HOST_URL;
        }
        return host;
    }
}
